/**
 * @fileoverview Tests for wrap-iife rule.
 * @author Ilya Volodin
 */

import type { TestCaseError } from '#test'
import type { MessageIds, RuleOptions } from './types'
import { run } from '#test'
import rule from './wrap-iife'

const wrapInvocationError: TestCaseError<MessageIds> = { messageId: 'wrapInvocation' }
const wrapExpressionError: TestCaseError<MessageIds> = { messageId: 'wrapExpression' }
const moveInvocationError: TestCaseError<MessageIds> = { messageId: 'moveInvocation' }

run<RuleOptions, MessageIds>({
  name: 'wrap-iife',
  rule,
  valid: [
    {
      code: '(function(){ }());',
      options: ['any'],
    },
    {
      code: '(function(){ })();',
      options: ['any'],
    },
    {
      code: '(function a(){ }());',
      options: ['any'],
    },
    {
      code: '(function a(){ })();',
      options: ['any'],
    },
    {
      code: '(function(){ }())',
      options: ['any'],
    },
    {
      code: '(function(){ })()',
      options: ['any'],
    },
    {
      code: '(function a(){ }());',
      options: ['outside'],
    },
    {
      code: '(function a(){ })();',
      options: ['inside'],
    },
    {
      code: 'foo.bar();',
      options: ['any'],
    },
    {
      code: 'var a = function(){return 1;};',
      options: ['any'],
    },
    {
      code: 'var a = ((function(){return 1;})());', // always allows existing extra parens (parens both inside and outside)
      options: ['any'],
    },
    {
      code: 'var a = ((function(){return 1;})());', // always allows existing extra parens (parens both inside and outside)
      options: ['inside'],
    },
    {
      code: 'var a = ((function(){return 1;})());', // always allows existing extra parens (parens both inside and outside)
      options: ['outside'],
    },
    {
      code: 'if (function (){}()) {}',
      options: ['any'],
    },
    {
      code: 'while (function (){}()) {}',
      options: ['any'],
    },
    {
      code: 'do {} while (function (){}())',
      options: ['any'],
    },
    {
      code: 'switch (function (){}()) {}',
      options: ['any'],
    },
    {
      code: 'with (function (){}()) {}',
      options: ['any'],
    },
    {
      code: 'foo(function (){}());',
      options: ['any'],
    },
    {
      code: 'new foo(function (){}());',
      options: ['any'],
    },
    {
      code: 'import(function (){}());',
      options: ['any'],
      parserOptions: { ecmaVersion: 2020 },
    },
    {
      code: 'if ((function (){})()) {}',
      options: ['any'],
    },
    {
      code: 'while (((function (){})())) {}',
      options: ['any'],
    },
    {
      code: 'if (function (){}()) {}',
      options: ['outside'],
    },
    {
      code: 'while (function (){}()) {}',
      options: ['outside'],
    },
    {
      code: 'do {} while (function (){}())',
      options: ['outside'],
    },
    {
      code: 'switch (function (){}()) {}',
      options: ['outside'],
    },
    {
      code: 'with (function (){}()) {}',
      options: ['outside'],
    },
    {
      code: 'foo(function (){}());',
      options: ['outside'],
    },
    {
      code: 'new foo(function (){}());',
      options: ['outside'],
    },
    {
      code: 'import(function (){}());',
      options: ['outside'],
      parserOptions: { ecmaVersion: 2020 },
    },
    {
      code: 'if ((function (){})()) {}',
      options: ['outside'],
    },
    {
      code: 'while (((function (){})())) {}',
      options: ['outside'],
    },
    {
      code: 'if ((function (){})()) {}',
      options: ['inside'],
    },
    {
      code: 'while ((function (){})()) {}',
      options: ['inside'],
    },
    {
      code: 'do {} while ((function (){})())',
      options: ['inside'],
    },
    {
      code: 'switch ((function (){})()) {}',
      options: ['inside'],
    },
    {
      code: 'with ((function (){})()) {}',
      options: ['inside'],
    },
    {
      code: 'foo((function (){})());',
      options: ['inside'],
    },
    {
      code: 'new foo((function (){})());',
      options: ['inside'],
    },
    {
      code: 'import((function (){})());',
      options: ['inside'],
      parserOptions: { ecmaVersion: 2020 },
    },
    {
      code: 'while (((function (){})())) {}',
      options: ['inside'],
    },
    {
      code: 'window.bar = (function() { return 3; }.call(this, arg1));',
      options: ['outside', { functionPrototypeMethods: true }],
    },
    {
      code: 'window.bar = (function() { return 3; }).call(this, arg1);',
      options: ['inside', { functionPrototypeMethods: true }],
    },
    {
      code: 'window.bar = (function() { return 3; }.apply(this, arg1));',
      options: ['outside', { functionPrototypeMethods: true }],
    },
    {
      code: 'window.bar = (function() { return 3; }).apply(this, arg1);',
      options: ['inside', { functionPrototypeMethods: true }],
    },
    {
      code: 'window.bar = function() { return 3; }.call(this, arg1);',
      options: ['inside'],
    },
    {
      code: 'window.bar = function() { return 3; }.call(this, arg1);',
      options: ['inside', {}],
    },
    {
      code: 'window.bar = function() { return 3; }.call(this, arg1);',
      options: ['inside', { functionPrototypeMethods: false }],
    },
    {
      code: 'window.bar = function() { return 3; }[call](this, arg1);',
      options: ['inside', { functionPrototypeMethods: true }],
    },
    {
      code: 'window.bar = function() { return 3; }[apply](this, arg1);',
      options: ['inside', { functionPrototypeMethods: true }],
    },
    {
      code: 'window.bar = function() { return 3; }[foo](this, arg1);',
      options: ['inside', { functionPrototypeMethods: true }],
    },
    {
      code: 'var a = function(){return 1;}.bind(this);',
      options: ['inside', { functionPrototypeMethods: true }],
    },
    {
      code: 'var a = function(){return 1;}.bind(this).apply(that);',
      options: ['inside', { functionPrototypeMethods: true }],
    },
    {
      code: 'var a = ((function(){return 1;}).call());', // always allows existing extra parens (parens both inside and outside)
      options: ['any', { functionPrototypeMethods: true }],
    },
    {
      code: 'var a = ((function(){return 1;}).call());', // always allows existing extra parens (parens both inside and outside)
      options: ['inside', { functionPrototypeMethods: true }],
    },
    {
      code: 'var a = ((function(){return 1;}).call());', // always allows existing extra parens (parens both inside and outside)
      options: ['outside', { functionPrototypeMethods: true }],
    },
    {
      code: 'if (function (){}.call()) {}',
      options: ['any', { functionPrototypeMethods: true }],
    },
    {
      code: 'while (function (){}.call()) {}',
      options: ['any', { functionPrototypeMethods: true }],
    },
    {
      code: 'do {} while (function (){}.call())',
      options: ['any', { functionPrototypeMethods: true }],
    },
    {
      code: 'switch (function (){}.call()) {}',
      options: ['any', { functionPrototypeMethods: true }],
    },
    {
      code: 'with (function (){}.call()) {}',
      options: ['any', { functionPrototypeMethods: true }],
    },
    {
      code: 'foo(function (){}.call())',
      options: ['any', { functionPrototypeMethods: true }],
    },
    {
      code: 'new foo(function (){}.call())',
      options: ['any', { functionPrototypeMethods: true }],
    },
    {
      code: 'import(function (){}.call())',
      options: ['any', { functionPrototypeMethods: true }],
      parserOptions: { ecmaVersion: 2020 },
    },
    {
      code: 'if ((function (){}).call()) {}',
      options: ['any', { functionPrototypeMethods: true }],
    },
    {
      code: 'while (((function (){}).call())) {}',
      options: ['any', { functionPrototypeMethods: true }],
    },
    {
      code: 'if (function (){}.call()) {}',
      options: ['outside', { functionPrototypeMethods: true }],
    },
    {
      code: 'while (function (){}.call()) {}',
      options: ['outside', { functionPrototypeMethods: true }],
    },
    {
      code: 'do {} while (function (){}.call())',
      options: ['outside', { functionPrototypeMethods: true }],
    },
    {
      code: 'switch (function (){}.call()) {}',
      options: ['outside', { functionPrototypeMethods: true }],
    },
    {
      code: 'with (function (){}.call()) {}',
      options: ['outside', { functionPrototypeMethods: true }],
    },
    {
      code: 'foo(function (){}.call())',
      options: ['outside', { functionPrototypeMethods: true }],
    },
    {
      code: 'new foo(function (){}.call())',
      options: ['outside', { functionPrototypeMethods: true }],
    },
    {
      code: 'import(function (){}.call())',
      options: ['outside', { functionPrototypeMethods: true }],
      parserOptions: { ecmaVersion: 2020 },
    },
    {
      code: 'if ((function (){}).call()) {}',
      options: ['outside', { functionPrototypeMethods: true }],
    },
    {
      code: 'while (((function (){}).call())) {}',
      options: ['outside', { functionPrototypeMethods: true }],
    },
    {
      code: 'if ((function (){}).call()) {}',
      options: ['inside', { functionPrototypeMethods: true }],
    },
    {
      code: 'while ((function (){}).call()) {}',
      options: ['inside', { functionPrototypeMethods: true }],
    },
    {
      code: 'do {} while ((function (){}).call())',
      options: ['inside', { functionPrototypeMethods: true }],
    },
    {
      code: 'switch ((function (){}).call()) {}',
      options: ['inside', { functionPrototypeMethods: true }],
    },
    {
      code: 'with ((function (){}).call()) {}',
      options: ['inside', { functionPrototypeMethods: true }],
    },
    {
      code: 'foo((function (){}).call())',
      options: ['inside', { functionPrototypeMethods: true }],
    },
    {
      code: 'new foo((function (){}).call())',
      options: ['inside', { functionPrototypeMethods: true }],
    },
    {
      code: 'import((function (){}).call())',
      options: ['inside', { functionPrototypeMethods: true }],
      parserOptions: { ecmaVersion: 2020 },
    },
    {
      code: 'if (((function (){}).call())) {}',
      options: ['inside', { functionPrototypeMethods: true }],
    },
  ],
  invalid: [
    {
      code: '0, function(){ }();',
      output: '0, (function(){ }());',
      errors: [wrapInvocationError],
    },
    {
      code: '[function(){ }()];',
      output: '[(function(){ }())];',
      errors: [wrapInvocationError],
    },
    {
      code: 'var a = function(){ }();',
      output: 'var a = (function(){ }());',
      errors: [wrapInvocationError],
    },
    {
      code: '(function(){ }(), 0);',
      output: '((function(){ }()), 0);',
      errors: [wrapInvocationError],
    },
    {
      code: '(function a(){ })();',
      output: '(function a(){ }());',
      options: ['outside'],
      errors: [moveInvocationError],
    },
    {
      code: '(function a(){ }());',
      output: '(function a(){ })();',
      options: ['inside'],
      errors: [wrapExpressionError],
    },
    {
      code: 'new foo((function (){}()))',
      output: 'new foo((function (){})())',
      options: ['inside'],
      errors: [wrapExpressionError],
    },
    {
      code: 'new (function (){}())',
      output: 'new ((function (){})())', // wrap function expression, but don't remove necessary grouping parens
      options: ['inside'],
      errors: [wrapExpressionError],
    },
    {
      code: 'new (function (){}())()',
      output: 'new ((function (){})())()', // wrap function expression, but don't remove necessary grouping parens
      options: ['inside'],
      errors: [wrapExpressionError],
    },
    {
      code: 'if (function (){}()) {}',
      output: 'if ((function (){})()) {}', // wrap function expression, but don't remove mandatory parens
      options: ['inside'],
      errors: [wrapExpressionError],
    },
    {
      code: 'if ((function (){}())) {}',
      output: 'if ((function (){})()) {}', // wrap function expression and remove unnecessary grouping parens around the call expression
      options: ['inside'],
      errors: [wrapExpressionError],
    },
    {
      code: 'while (function (){}()) {}',
      output: 'while ((function (){})()) {}', // wrap function expression, but don't remove mandatory parens
      options: ['inside'],
      errors: [wrapExpressionError],
    },
    {
      code: 'do {} while (function (){}())',
      output: 'do {} while ((function (){})())', // wrap function expression, but don't remove mandatory parens
      options: ['inside'],
      errors: [wrapExpressionError],
    },
    {
      code: 'switch (function (){}()) {}',
      output: 'switch ((function (){})()) {}', // wrap function expression, but don't remove mandatory parens
      options: ['inside'],
      errors: [wrapExpressionError],
    },
    {
      code: 'with (function (){}()) {}',
      output: 'with ((function (){})()) {}', // wrap function expression, but don't remove mandatory parens
      options: ['inside'],
      errors: [wrapExpressionError],
    },
    {
      code: 'foo(function (){}())',
      output: 'foo((function (){})())', // wrap function expression, but don't remove mandatory parens
      options: ['inside'],
      errors: [wrapExpressionError],
    },
    {
      code: 'new foo(function (){}())',
      output: 'new foo((function (){})())', // wrap function expression, but don't remove mandatory parens
      options: ['inside'],
      errors: [wrapExpressionError],
    },
    {
      code: 'import(function (){}())',
      output: 'import((function (){})())', // wrap function expression, but don't remove mandatory parens
      options: ['inside'],
      parserOptions: { ecmaVersion: 2020 },
      errors: [wrapExpressionError],
    },
    {

      // Ensure all comments get preserved when autofixing.
      code: '( /* a */ function /* b */ foo /* c */ ( /* d */ bar /* e */ ) /* f */ { /* g */ return; /* h */ } /* i */ ( /* j */ baz /* k */) /* l */ ) /* m */ ;',
      output: '( /* a */ function /* b */ foo /* c */ ( /* d */ bar /* e */ ) /* f */ { /* g */ return; /* h */ }) /* i */ ( /* j */ baz /* k */) /* l */  /* m */ ;',
      options: ['inside'],
      errors: [wrapExpressionError],
    },
    {
      code: '( /* a */ function /* b */ foo /* c */ ( /* d */ bar /* e */ ) /* f */ { /* g */ return; /* h */ } /* i */ ) /* j */ ( /* k */ baz /* l */) /* m */ ;',
      output: '( /* a */ function /* b */ foo /* c */ ( /* d */ bar /* e */ ) /* f */ { /* g */ return; /* h */ } /* i */  /* j */ ( /* k */ baz /* l */)) /* m */ ;',
      options: ['outside'],
      errors: [moveInvocationError],
    },
    {
      code: '+function(){return 1;}()',
      output: '+(function(){return 1;}())',
      options: ['outside'],
      errors: [wrapInvocationError],
    },
    {
      code: '+function(){return 1;}()',
      output: '+(function(){return 1;})()',
      options: ['inside'],
      errors: [wrapInvocationError],
    },
    {
      code: 'window.bar = function() { return 3; }.call(this, arg1);',
      output: 'window.bar = (function() { return 3; }).call(this, arg1);',
      options: ['inside', { functionPrototypeMethods: true }],
      errors: [wrapInvocationError],
    },
    {
      code: 'window.bar = function() { return 3; }[\'call\'](this, arg1);',
      output: 'window.bar = (function() { return 3; })[\'call\'](this, arg1);',
      options: ['inside', { functionPrototypeMethods: true }],
      errors: [wrapInvocationError],
    },
    {
      code: 'window.bar = function() { return 3; }.call(this, arg1);',
      output: 'window.bar = (function() { return 3; }.call(this, arg1));',
      options: ['outside', { functionPrototypeMethods: true }],
      errors: [wrapInvocationError],
    },
    {
      code: 'window.bar = (function() { return 3; }.call(this, arg1));',
      output: 'window.bar = (function() { return 3; }).call(this, arg1);',
      options: ['inside', { functionPrototypeMethods: true }],
      errors: [wrapExpressionError],
    },
    {
      code: 'window.bar = (function() { return 3; }).call(this, arg1);',
      output: 'window.bar = (function() { return 3; }.call(this, arg1));',
      options: ['outside', { functionPrototypeMethods: true }],
      errors: [moveInvocationError],
    },
    {
      code: 'new (function (){}.call())',
      output: 'new ((function (){}).call())', // wrap function expression, but don't remove necessary grouping parens
      options: ['inside', { functionPrototypeMethods: true }],
      errors: [wrapExpressionError],
    },
    {
      code: 'new (function (){}.call())()',
      output: 'new ((function (){}).call())()', // wrap function expression, but don't remove necessary grouping parens
      options: ['inside', { functionPrototypeMethods: true }],
      errors: [wrapExpressionError],
    },
    {
      code: 'if (function (){}.call()) {}',
      output: 'if ((function (){}).call()) {}', // wrap function expression, but don't remove mandatory parens
      options: ['inside', { functionPrototypeMethods: true }],
      errors: [wrapExpressionError],
    },
    {
      code: 'if ((function (){}.call())) {}',
      output: 'if ((function (){}).call()) {}', // wrap function expression and remove unnecessary grouping parens around the call expression
      options: ['inside', { functionPrototypeMethods: true }],
      errors: [wrapExpressionError],
    },
    {
      code: 'while (function (){}.call()) {}',
      output: 'while ((function (){}).call()) {}', // wrap function expression, but don't remove mandatory parens
      options: ['inside', { functionPrototypeMethods: true }],
      errors: [wrapExpressionError],
    },
    {
      code: 'do {} while (function (){}.call())',
      output: 'do {} while ((function (){}).call())', // wrap function expression, but don't remove mandatory parens
      options: ['inside', { functionPrototypeMethods: true }],
      errors: [wrapExpressionError],
    },
    {
      code: 'switch (function (){}.call()) {}',
      output: 'switch ((function (){}).call()) {}', // wrap function expression, but don't remove mandatory parens
      options: ['inside', { functionPrototypeMethods: true }],
      errors: [wrapExpressionError],
    },
    {
      code: 'with (function (){}.call()) {}',
      output: 'with ((function (){}).call()) {}', // wrap function expression, but don't remove mandatory parens
      options: ['inside', { functionPrototypeMethods: true }],
      errors: [wrapExpressionError],
    },
    {
      code: 'foo(function (){}.call())',
      output: 'foo((function (){}).call())', // wrap function expression, but don't remove mandatory parens
      options: ['inside', { functionPrototypeMethods: true }],
      errors: [wrapExpressionError],
    },
    {
      code: 'new foo(function (){}.call())',
      output: 'new foo((function (){}).call())', // wrap function expression, but don't remove mandatory parens
      options: ['inside', { functionPrototypeMethods: true }],
      errors: [wrapExpressionError],
    },
    {
      code: 'import(function (){}.call())',
      output: 'import((function (){}).call())', // wrap function expression, but don't remove mandatory parens
      options: ['inside', { functionPrototypeMethods: true }],
      parserOptions: { ecmaVersion: 2020 },
      errors: [wrapExpressionError],
    },

    // Optional chaining
    {
      code: 'window.bar = function() { return 3; }.call?.(this, arg1);',
      output: 'window.bar = (function() { return 3; }).call?.(this, arg1);',
      options: ['inside', { functionPrototypeMethods: true }],
      parserOptions: { ecmaVersion: 2020 },
      errors: [wrapInvocationError],
    },
    {
      code: 'window.bar = function() { return 3; }?.call(this, arg1);',
      output: 'window.bar = (function() { return 3; })?.call(this, arg1);',
      options: ['inside', { functionPrototypeMethods: true }],
      parserOptions: { ecmaVersion: 2020 },
      errors: [wrapInvocationError],
    },
    {
      code: 'window.bar = (function() { return 3; }?.call)(this, arg1);',
      output: 'window.bar = ((function() { return 3; })?.call)(this, arg1);',
      options: ['inside', { functionPrototypeMethods: true }],
      parserOptions: { ecmaVersion: 2020 },
      errors: [wrapInvocationError],
    },
    {
      code: 'new (function () {} ?.());',
      output: 'new ((function () {}) ?.());',
      options: ['inside'],
      parserOptions: { ecmaVersion: 2020 },
      errors: [wrapExpressionError],
    },
  ],
})
